As Interfaces Gráficas com Usuário (GUI, Graphic User Interface) se popularizaram no ambiente desktop, devido à facilidade de uso e a produtividade. Existem hoje muitas bibliotecas disponíveis para a construção de aplicações GUI, tais como: GTK+, QT, TK e wxWidgets.
Interfaces gráficas geralmente utilizam a metáfora do desktop, um espaço em duas dimensões, é que ocupado por janelas retangulares, que representam aplicativos, propriedades ou documentos.
As janelas podem conter diversos tipos de controles (objetos utilizados para interagir com o usuário ou para apresentar informações) e containers (objetos que servem de repositório para coleções de outros objetos).
Na maior parte do tempo, a interface gráfica espera por eventos e responde de acordo. Os eventos podem ser resultado da interação do usuário, como cliques e arrastar de mouse ou digitação, ou ainda de eventos de sistema, como o relógio da máquina. A reação a um evento qualquer é definida através de funções callback (funções que são passadas como argumento para outras rotinas).
Controles mais usados:
Controles podem ter aceleradores (teclas de atalho) associados a eles.
Containers mais usados:
Para lidar com eventos de tempo, as interfaces gráficas implementam um recurso chamado temporizador (timer) que evoca a função callback depois de um certo tempo programado.
O GTK+ (GIMP Toolkit) é uma biblioteca Open Source escrita em linguagem C. Originalmente concebida para ser usada pelo GIMP, é compatível com as plataformas mais utilizadas atualmente e rica em recursos, entre eles, um construtor de interfaces chamado Glade.
Interface do Glade:
O GTK+ é usado pelo GNOME (ambiente desktop Open Source) e por diversos aplicativos, como os portes do Mozilla Firefox e do BrOffice.org para sistemas UNIX. O GTK+ pode ser usado no Python através do pacote PyGTK. Os portes das bibliotecas para Windows podem ser encontrados em:
Embora seja possível criar interfaces inteiramente usando código, é mais produtivo construir a interface em um software apropriado. O Glade gera arquivos XML com extensão “.glade”, que podem ser lidos por programas que usam GTK+, automatizando o processo de criar interfaces gráficas.
Roteiro básico para construir uma interface:
No Glade:
No Python:
signal_autoconnect()
.gtk.main()
.Exemplo (relógio):
No Glade:
Janela principal do relógio:
Código em Python:
In [ ]:
"""
Um relógio com GTK.
"""
import datetime
# GTK e outros módulos associados
import gtk
import gtk.glade
import gobject
import pango
class Relogio(object):
"""
Implementa a janela principal do programa.
"""
def __init__(self):
"""
Inicializa a classe.
"""
# Carrega a interface
self.tree = gtk.glade.XML('relogio.glade', 'main')
# Liga os eventos
callbacks = {
'on_main_destroy': self.on_main_destroy,
'on_imagemenuitem5_activate': self.on_main_destroy,
'on_imagemenuitem10_activate': self.on_imagemenuitem10_activate
}
self.tree.signal_autoconnect(callbacks)
# Coloca um título na janela
self.tree.get_widget('main').set_title('Relógio')
# O rótulo que reberá a hora
self.hora = self.tree.get_widget('lbl_hora')
# A barra de status que reberá a data
self.data = self.tree.get_widget('sts_data')
print dir(self.data)
# Muda a fonte do rótulo
self.hora.modify_font(pango.FontDescription('verdana 28'))
# Um temporizador para manter a hora atualizada
self.timer = gobject.timeout_add(1000, self.on_timer)
def on_imagemenuitem10_activate(self, widget):
"""
Cria a janela de "Sobre".
"""
# Caixa de dialogo
dialog = gtk.MessageDialog(parent=self.tree.get_widget('main'),
flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
type=gtk.MESSAGE_OTHER, buttons=gtk.BUTTONS_OK,
message_format='Primeiro exemplo usando GTK.')
dialog.set_title('Sobre')
dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)
# Exibe a caixa
dialog.run()
dialog.destroy()
return
def on_timer(self):
"""
Rotina para o temporizador.
"""
# Pega a hora do sistema
hora = datetime.datetime.now().time().isoformat().split('.')[0]
# Muda o texto do rótulo
self.hora.set_text(hora)
# Pega a data do sistema em formato ISO
data = datetime.datetime.now().date().isoformat()
data = 'Data: ' + '/'.join(data.split('-')[::-1])
# Coloca a data na barra de status
self.data.push(0, data)
# Verdadeiro faz com que o temporizador rode de novo
return True
def on_main_destroy(self, widget):
"""
Termina o programa.
"""
raise SystemExit
if __name__ == "__main__":
# Inicia a GUI
relogio = Relogio()
gtk.main()
Arquivo "relogio.glade":
In [ ]:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.3 on Sat May 03 14:06:18 2008 -->
<glade-interface>
<widget class="GtkWindow" id="main">
<property name="visible">True</property>
<property name="resizable">False</property>
<property name="window_position">GTK_WIN_POS_CENTER</property>
<signal name="destroy" handler="on_main_destroy"/>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<child>
<widget class="GtkMenuBar" id="menubar1">
<property name="visible">True</property>
<child>
<widget class="GtkMenuItem" id="menuitem1">
<property name="visible">True</property>
<property name="label" translatable="yes">_Arquivo</property>
<property name="use_underline">True</property>
<child>
<widget class="GtkMenu" id="menu1">
<property name="visible">True</property>
<child>
<widget class="GtkImageMenuItem" id="imagemenuitem5">
<property name="visible">True</property>
<property name="label" translatable="yes">gtk-quit</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_imagemenuitem5_activate"/>
</widget>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="menuitem4">
<property name="visible">True</property>
<property name="label" translatable="yes">Aj_uda</property>
<property name="use_underline">True</property>
<child>
<widget class="GtkMenu" id="menu3">
<property name="visible">True</property>
<child>
<widget class="GtkImageMenuItem" id="imagemenuitem10">
<property name="visible">True</property>
<property name="label" translatable="yes">gtk-about</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_imagemenuitem10_activate"/>
</widget>
</child>
</widget>
</child>
</widget>
</child>
</widget>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="lbl_hora">
<property name="width_request">300</property>
<property name="height_request">150</property>
<property name="visible">True</property>
<property name="xpad">5</property>
<property name="ypad">5</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkStatusbar" id="sts_data">
<property name="visible">True</property>
<property name="spacing">2</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>
Exemplo (rodando programas):
No Glade:
Janela principal:
Código em Python:
In [ ]:
"""
Rodando programas com GTK.
"""
import subprocess
import gtk
import gtk.glade
import gobject
import pango
class Exec(object):
"""
Janela principal.
"""
def __init__(self):
"""
Inicializa a classe.
"""
# Carrega a interface
self.tree = gtk.glade.XML('cmd.glade', 'main')
# Liga os eventos
callbacks = {
'on_main_destroy': self.on_main_destroy,
'on_btn_fechar_clicked': self.on_main_destroy,
'on_btn_rodar_clicked': self.on_btn_rodar_clicked
}
self.tree.signal_autoconnect(callbacks)
def on_btn_rodar_clicked(self, widget):
"""
Roda o comando.
"""
ntr_cmd = self.tree.get_widget('ntr_cmd')
chk_shell = self.tree.get_widget('chk_shell')
cmd = ntr_cmd.get_text()
if cmd:
# chk_shell.state será 1 se chk_shell estiver marcado
if chk_shell.state:
cmd = 'cmd start cmd /c ' + cmd
subprocess.Popen(args=cmd)
else:
# Caixa de dialogo
dialog = gtk.MessageDialog(parent=self.tree.get_widget('main'),
flags=gtk.DIALOG_MODAL |
gtk.DIALOG_DESTROY_WITH_PARENT,
type=gtk.MESSAGE_OTHER, buttons=gtk.BUTTONS_OK,
message_format='Digite um comando.')
dialog.set_title('Mensagem')
dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)
# Exibe a caixa
dialog.run()
dialog.destroy()
return True
def on_main_destroy(self, widget):
"""
Termina o programa.
"""
raise SystemExit
if __name__ == "__main__":
# Inicia a GUI
exe = Exec()
gtk.main()
O arquivo “cmd.glade”:
In [ ]:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.3 on Tue May 27 23:44:03 2008 -->
<glade-interface>
<widget class="GtkWindow" id="main">
<property name="width_request">380</property>
<property name="height_request">100</property>
<property name="visible">True</property>
<property name="title" translatable="yes">Entre com um comando...</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">GTK_WIN_POS_CENTER</property>
<signal name="destroy" handler="on_main_destroy"/>
<child>
<widget class="GtkFixed" id="fixed1">
<property name="width_request">380</property>
<property name="height_request">100</property>
<property name="visible">True</property>
<child>
<widget class="GtkButton" id="btn_rodar">
<property name="width_request">100</property>
<property name="height_request">29</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">_Rodar</property>
<property name="use_underline">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_btn_rodar_clicked"/>
</widget>
<packing>
<property name="x">167</property>
<property name="y">61</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="btn_fechar">
<property name="width_request">100</property>
<property name="height_request">29</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">_Fechar</property>
<property name="use_underline">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_btn_fechar_clicked"/>
</widget>
<packing>
<property name="x">272</property>
<property name="y">61</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="ntr_cmd">
<property name="width_request">365</property>
<property name="height_request">29</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
</widget>
<packing>
<property name="x">9</property>
<property name="y">14</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="chk_shell">
<property name="width_request">136</property>
<property name="height_request">29</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">_Janela de texto</property>
<property name="use_underline">True</property>
<property name="response_id">0</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="x">11</property>
<property name="y">59</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>
Além do Glade, também existe o Gaspacho, outro construtor de interfaces que também gera arquivos XML no padrão do Glade.
O pacote wxPython é basicamente um wrapper para o toolkit (conjunto de ferramentas e bibliotecas) wxWidgets, desenvolvido em C++. Principais características:
A forma geral de funcionamento é similar ao GTK+: o framework controla a interação com o usuário através de um laço que detecta eventos e ativa as rotinas correspondentes.
A maneira mais usual de implementar uma interface gráfica através do wxPython consiste em:
Exemplo (caixa de texto):
In [ ]:
# importa wxPython
import wx
class Main(wx.Frame):
"""
Classe que define a janela principal do programa.
"""
def __init__(self, parent, id, title):
"""
Inicializa a classe.
"""
# Define a janela usando o __init__ da classe mãe
wx.Frame.__init__(self, parent, id, title, size=(600, 400))
# Cria uma caixa de texto
self.text = wx.TextCtrl(self, style=wx.TE_MULTILINE)
# Pega o fonte do programa (decodificado para latin1)
font = file(__file__, 'rb').read().decode('latin1')
# Carrega o fonte do programa na caixa de texto
self.text.SetLabel(font)
# Mostra a janela
self.Show(True)
if __name__ == '__main__':
# Cria um objeto "aplicação" do wxPython
app = wx.App()
# Cria um objeto "janela" a partir da classe
frame = Main(None, wx.ID_ANY, 'Fonte')
# Inicia o loop de tratamento de eventos
app.MainLoop()
Janela do exemplo:
Exemplo (temporizador):
In [ ]:
import wx
import time
class Main(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(150, 80))
clock = time.asctime().split()[3]
# Cria um rótulo de texto
self.control = wx.StaticText(self, label=clock)
# Muda a fonte
self.control.SetFont(wx.Font(22, wx.SWISS, wx.NORMAL, wx.BOLD))
# Cria um timer
TIMER_ID = 100
self.timer = wx.Timer(self, TIMER_ID)
# Inicia o timer
self.timer.Start(1000)
# Associa os métodos com os eventos
wx.EVT_TIMER(self, TIMER_ID, self.on_timer)
wx.EVT_CLOSE(self, self.on_close)
self.Show(True)
def on_timer(self, event):
# Atualiza o relógio
clock = time.asctime().split()[3]
self.control.SetLabel(clock)
def on_close(self, event):
# Para o timer
self.timer.Stop()
self.Destroy()
app = wx.App()
Main(None, wx.ID_ANY, 'Relógio')
app.MainLoop()
Interface:
Exemplo (barra de menus):
In [ ]:
import wx
# Identificadores para as opções do menu
ID_FILE_OPEN = wx.NewId()
ID_FILE_SAVE = wx.NewId()
ID_FILE_EXIT = wx.NewId()
ID_HELP_ABOUT = wx.NewId()
class Main(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
# Cria o menu arquivo
filemenu = wx.Menu()
# Cria as opções
filemenu.Append(ID_FILE_OPEN, 'Abrir arquivo...')
filemenu.Append(ID_FILE_SAVE, 'Salvar')
filemenu.AppendSeparator()
filemenu.Append(ID_FILE_EXIT, 'Sair')
# Cria o menu ajuda
helpmenu = wx.Menu()
helpmenu.Append(ID_HELP_ABOUT, 'Sobre...')
# Cria o menu
menubar = wx.MenuBar()
menubar.Append(filemenu, 'Arquivo')
menubar.Append(helpmenu, 'Ajuda')
self.SetMenuBar(menubar)
# Associa métodos aos eventos de menu
wx.EVT_MENU(self, ID_FILE_OPEN, self.on_open)
wx.EVT_MENU(self, ID_FILE_SAVE, self.on_save)
wx.EVT_MENU(self, ID_FILE_EXIT, self.on_exit)
wx.EVT_MENU(self, ID_HELP_ABOUT, self.about)
# Cria uma caixa de texto
self.control = wx.TextCtrl(self, 1,
style=wx.TE_MULTILINE)
self.fn = ''
def on_open(self, evt):
# Abre uma caixa de dialogo escolher arquivo
dialog = wx.FileDialog(None, style=wx.OPEN)
d = dialog.ShowModal()
if d == wx.ID_OK:
# Pega o arquivo escolhido
self.fn = dialog.GetPath()
# Muda o título da janela
self.SetTitle(self.fn)
# Carrega o texto na caixa de texto
self.control.SetLabel(file(self.fn, 'rb').read())
dialog.Destroy()
def on_save(self, evt):
# Salva o texto na caixa de texto
if self.fn:
file(self.fn, 'wb').write(self.control.GetLabel())
def on_exit(self, evt):
# Fecha a janela principal
self.Close(True)
def about(self, evt):
dlg = wx.MessageDialog(self,
'Exemplo wxPython', 'Sobre...',
wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
app = wx.App()
frame = Main(None , wx.ID_ANY, 'Isto é quase um editor...')
frame.Show(True)
app.MainLoop()
Janela principal:
Exemplo (caixa de mensagem):
In [ ]:
import wx
class Main(wx.Frame):
def __init__(self, parent, id, title):
# Cria janela
wx.Frame.__init__(self, parent, id, title, size=(300, 150))
self.Centre()
self.Show(True)
# Cria um texto estático
self.text = wx.StaticText(self, label='Entre com uma expressão:',
pos=(10, 10))
# Cria uma caixa de edição de texto
self.edit = wx.TextCtrl(self, size=(250, -1), pos=(10, 30))
# Cria um botão
self.button = wx.Button(self, label='ok', pos=(10, 60))
# Conecta um método ao botão
self.button.Bind(wx.EVT_BUTTON, self.on_button)
def on_button(self, event):
# Pega o valor da caixa de texto
txt = self.edit.GetValue()
# Tenta resolver e apresentar a expressão
try:
wx.MessageBox(txt + ' = ' + str(eval(txt)), 'Resultado')
# Se algo inesperado ocorrer
except:
wx.MessageBox('Expressão inválida', 'Erro')
app = wx.App()
Main(None, -1, 'Teste de MessageBox')
app.MainLoop()
Janela principal e caixa de mensagem:
O wxPython oferece uma variedade enorme de controles prontos, que ser no programa de demonstração que é distribuído junto com a documentação e os exemplos.
Qt é um toolkit desenvolvido em C++ e é utilizado por diversos programas, incluindo o ambiente de desktop gráfico KDE e seus aplicativos. Embora o Qt seja mais usado para a criação de aplicativos GUI, ele também inclui bibliotecas com outras funcionalidades, como acesso a banco de dados, comunicação de rede e controle de threads, entre outras. PyQt é um binding que permite o uso do Qt no Python, disponível sob a licença GPL.
A Qt na versão 4 possui dois módulos principais, chamados QtGui, que define as rotinas de interface, e QtCore, que define estruturas essenciais para o funcionamento do toolkit, como, por exemplo, os sinais (eventos).
Exemplo:
In [ ]:
import sys
from PyQt4 import QtGui, QtCore
class Main(QtGui.QWidget):
"""
Janela principal
"""
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# Muda a geometria da janela
self.setGeometry(200, 200, 200, 100)
# Muda o título
self.setWindowTitle('Teste')
# Cria um botão
quit = QtGui.QPushButton('Fechar', self)
quit.setGeometry(10, 10, 60, 35)
# Conecta o sinal gerado pelo botão com a função
# que encerra o programa
self.connect(quit, QtCore.SIGNAL('clicked()'),
QtGui.qApp, QtCore.SLOT('quit()'))
# Cria um objeto "aplicação Qt", que trata os eventos
app = QtGui.QApplication(sys.argv)
# Cria a janela principal
qb = Main()
qb.show()
# Inicia a "aplicação Qt"
sys.exit(app.exec_())
Janela principal:
Um dos maiores atrativos do PyQt é o GUI Builder (ferramenta para a construção de interfaces) Qt Designer. Os arquivos XML gerados pelo Qt Designer (com a extensão .ui) podem ser convertidos em módulos Python através do utilitário pyuic.
Para gerar o módulo Python a partir do arquivo criado no Qt Designer:
pyuic dialog.ui -o dialog.py
No qual dialog.ui
é o arquivo de interface e dialog.py
é o módulo.
Exemplo de arquivo gerado pelo Qt Designer (dialog.ui):
In [ ]:
<ui version="4.0" >
<class>Dialog</class>
<widget class="QDialog" name="Dialog" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>116</width>
<height>108</height>
</rect>
</property>
<property name="windowTitle" >
<string>Dialog</string>
</property>
<widget class="QPushButton" name="msg" >
<property name="geometry" >
<rect>
<x>20</x>
<y>20</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text" >
<string>Mensagem</string>
</property>
</widget>
<widget class="QPushButton" name="fim" >
<property name="geometry" >
<rect>
<x>20</x>
<y>60</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text" >
<string>Fechar</string>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>fim</sender>
<signal>clicked()</signal>
<receiver>Dialog</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel" >
<x>57</x>
<y>71</y>
</hint>
<hint type="destinationlabel" >
<x>57</x>
<y>53</y>
</hint>
</hints>
</connection>
</connections>
</ui>
O arquivo de interface define uma janela, da classe QDialog, chamada “Dialog”, com dois botões, da classe QPushButton, chamados “msg” e “fim”.
Exemplo usando o módulo criado pelo Qt Designer:
In [ ]:
import sys
import time
from PyQt4 import QtCore, QtGui
# Módulo gerado pelo pyuic
from dialog import Ui_Dialog
class Main(QtGui.QMainWindow):
"""
Janela principal
"""
def __init__(self, parent=None):
"""
Inicialização da janela
"""
QtGui.QWidget.__init__(self, parent)
# Cria um objeto a partir da interface gerada pelo pyuic
self.ui = Ui_Dialog()
self.ui.setupUi(self)
# Conecta o método ao botão que foi definido através do Qt Designer
self.connect(self.ui.msg, QtCore.SIGNAL('clicked()'),
self.show_msg)
def show_msg(self):
"""
Método que evoca a caixa de mensagem
"""
reply = QtGui.QMessageBox.question(self, 'Messagem',
'Hora: ' + time.asctime().split()[3],
QtGui.QMessageBox.Ok)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = Main()
myapp.show()
sys.exit(app.exec_())
Janela principal e caixa de mensagem:
Também está disponível um binding LGPL similar ao PyQt, chamado PySide.
Outras funcionalidades associadas a interface gráfica podem ser obtidas usando outros módulos, como o pySystray, que implementa a funcionalidade que permite que o aplicativo use a bandeja de sistema no Windows.
In [1]:
Out[1]: